#include "comment.h"

int count = 0; // 用来统计有多少客户端连接，有时候也会作为下标使用
#define MAX_EVENT 3000

LOGIN_INFO loginInfoArr[1000]; // 登录信息数组
int loginCount = 0;            // 登录的人数，有时候也作为下标用

// 把聊天记录保存到文件中
void saveChatRecordToFile(SINGLE_CHAT *pSc)
{
     int fd = open("chatRecord.dat", O_WRONLY | O_APPEND);
     printf("聊天记录保存,内容是：%s %s %s %ld", pSc->selfName, pSc->destName, pSc->text, pSc->t);
     write(fd, pSc, sizeof(SINGLE_CHAT));
     close(fd);
}

// 查询聊天记录文件里面的聊天记录，并发给客户端
void queryChatRecord(int sID, SINGLE_CHAT *pSc)
{
     int fd = open("chatRecord.dat", O_RDONLY);
     SINGLE_CHAT sc;
     int ret = 0;
     MSG msg;
     // 读取所有聊天记录，并判断是否是指定的两个用户的记录
     while (ret = read(fd, &sc, sizeof(SINGLE_CHAT)))
     {
          // 如果要查询的两个名字和文件中聊天记录的两个名字相同，说明该聊天记录时要查询的
          if ((strcmp(pSc->selfName, sc.selfName) == 0 || strcmp(pSc->destName, sc.selfName) == 0) &&
              (strcmp(pSc->selfName, sc.destName) == 0 || strcmp(pSc->destName, sc.destName) == 0))
          {
               msg.type = queryChatRec; // 消息类型时查询聊天记录
               memcpy(msg.buf, &sc, sizeof(SINGLE_CHAT));
               // 把包含有一条聊天记录的消息发送给客户端
               int ret = send(sID, &msg, sizeof(msg), 0);
               if (ret == -1)
               {
                    printf("发送数据出错了\n");
                    break;
               }
          }
     }

     msg.type = queryOver; // 查询结束
     //  把查询的消息发送给客户端
     ret = send(sID, &msg, sizeof(msg), 0);
     if (ret == -1)
     {
          printf("发送数据出错了\n");
     }

     close(fd);
}

int work(int sID, MSG msg)
{
     if (msg.type == quit)
     {
          // // 遍历所有的客户端地址
          // for (int i = 0; i < count; i++)
          // {
          //      // 说明下标为i的元素是需要从数组中删除的
          //      if (clientAddr.sin_addr.s_addr == clientArr[i].sin_addr.s_addr && clientAddr.sin_port == clientArr[i].sin_port)
          //      {
          //           // 如果不是最后一个元素
          //           if (i != count - 1)
          //           {
          //                // 把数组后面的元素往前移动
          //                for (size_t j = i; j < count - 1; j++)
          //                {
          //                     clientArr[j] = clientArr[j + 1];
          //                }
          //           }
          //           // 总连接上的客户端数量-1
          //           count--;
          //           printf("有客户端退出，总连接数变为:%d\n", count);
          //      }
          // }
     }
     else if (msg.type == regist)
     {
          // 把缓冲区的类型转为注册
          RGU *pRgu = (RGU *)msg.buf;
          // 把收到的用户注册信息存入到文件中
          int fd = open("user.dat", O_CREAT | O_WRONLY | O_APPEND, 0777);
          write(fd, pRgu, sizeof(RGU));
          close(fd);
     }
     else if (msg.type == login)
     {
          // 把缓冲区的类型转为注册，因为注册和登录用的是同一个结构体
          RGU *pRgu = (RGU *)msg.buf;
          // 把收到的用户注册信息存入到文件中
          int fd = open("user.dat", O_RDONLY);
          if (fd == -1)
          {
               perror("打开用户文件出错");
               return 0;
          }

          int ret = 0;
          RGU rgu;
          // 因为登录成功了，所以把消息类型改为ok，再发回去
          msg.type = no;
          printf("收到登录消息，用户是：%s 密码是：%s\n", pRgu->name, pRgu->pwd);
          // 读取文件中的用户，用来和网络发来的用户名和密码进行比较
          while (ret = read(fd, &rgu, sizeof(rgu)))
          {
               printf("当前和%s %s比较\n", rgu.name, rgu.pwd);
               // 判断用户名和密码是否匹配
               if (strcmp(rgu.name, pRgu->name) == 0 && strcmp(rgu.pwd, pRgu->pwd) == 0)
               {
                    printf("比较成功了\n");
                    // 因为登录成功了，所以把消息类型改为ok，再发回去
                    msg.type = ok;
                    // 把登录成功的用户名和地址保存起来
                    strcpy(loginInfoArr[loginCount].name, rgu.name);
                    loginInfoArr[loginCount].fd = sID; // 登录成功后记住用户的套接字
                    loginCount++;
                    break;
               }
          }
          close(fd);

          printf("消息类型为：%d\n", msg.type);
          // 把消息发回客户端，告诉它登录成功了
          ret = send(sID, &msg, sizeof(msg), 0);
          if (ret == -1)
          {
               printf("发送数据出错了\n");
          }
          close(fd);
     }
     else if (msg.type == singleChat) // 单聊
     {
          SINGLE_CHAT *pSC = (SINGLE_CHAT *)msg.buf;
          printf("收到了%s发来的消息，目标是：%s . 内容是:%s\n", pSC->selfName, pSC->destName, pSC->text);
          // 遍历所有登录的人
          for (size_t i = 0; i < loginCount; i++)
          {
               // 判断下标为i的用户是不是要私聊的对象
               if (strcmp(loginInfoArr[i].name, pSC->destName) == 0)
               {
                    printf("找到了要聊天的对象，现在开始发消息给他\n");
                    pSC->t = time(0); // 记录发消息的时间
                    // 把消息发送给服务器
                    int ret = send(sID, &msg, sizeof(msg), 0);
                    if (ret == -1)
                    {
                         printf("发送数据出错了\n");
                         break;
                    }
                    saveChatRecordToFile(pSC);
               }
          }
     }
     else if (msg.type == queryChatRec)
     {
          SINGLE_CHAT *pSC = (SINGLE_CHAT *)msg.buf;
          queryChatRecord(sID, pSC);
     }
     else
     {
          // // 遍历所有的客户端地址
          // for (int i = 0; i < count; i++)
          // {
          //      // 说明下标为i的元素是需要从数组中删除的
          //      if (clientAddr.sin_addr.s_addr != clientArr[i].sin_addr.s_addr || clientAddr.sin_port != clientArr[i].sin_port)
          //      {
          //           // 把消息发送给服务器
          //           int ret = sendto(sID, &msg, sizeof(msg), 0, (struct sockaddr *)&clientArr[i], sizeof(clientArr[i]));
          //           if (ret == -1)
          //           {
          //                printf("发送数据出错了\n");
          //                break;
          //           }
          //           else
          //           {
          //                printf("转发了一条消息%s\n", msg.buf);
          //           }
          //           // printf("我要把消息打印出来:%s\n", buf);
          //      }
          // }
     }
     return 1;
}

int main(int c, char *arg[])
{
     struct sockaddr_in sin;
     sin.sin_family = AF_INET;   // IPV4的网络地址版本
     sin.sin_port = htons(9000); // 把9000号端口转为网络字节序并赋值给端口参数

     u_int32_t ip = 0;
     // 把字符串ip地址转为网络字节序ip
     inet_pton(AF_INET, "192.168.50.18", &ip);
     sin.sin_addr.s_addr = ip;

     // 创建一个tcp套接字，参数1指定地址族，参数2指定协议，SOCK_STREAM代表是TCP，SOCK_DGRAM代表UDP
     int sID = socket(AF_INET, SOCK_STREAM, 0);
     // 使端口号马上能被重新使用
     int opt = 1;
     if (setsockopt(sID, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
     {
          perror("设置套接字属性出错");
     }
     // 把套接字和地址绑定（手机+手机号）
     if (-1 == bind(sID, (struct sockaddr *)&sin, sizeof(sin)))
     {
          perror("绑定地址失败，请等待30秒或者换一个端口试试");
          return 0;
     }

     // 打开监听功能，监听绑定地址上的来访请求，5代表同时允许5个客户端排队连接
     listen(sID, 5);

     // 定义MAX_EVENT个事件结构体，每个结构体中都有感兴趣的事件和套接字
     struct epoll_event ev, events[MAX_EVENT];

     // 创建epoll对象
     int epoll_id = epoll_create(MAX_EVENT); // 创建一个可以管理MAX_EVENT个套接字事件的epoll对象

     ev.events = EPOLLIN; // 感兴趣的事件为有数据可读
     ev.data.fd = sID;    // 感兴趣的套接字

     // epoll操作和控制，EPOLL_CTL_ADD是往epoll对象中添加结构体
     // 该函数内部会复制最后的结构体ev，所以ev可以重复使用
     epoll_ctl(epoll_id, EPOLL_CTL_ADD, sID, &ev);

     while (1)
     {
          // count是当前一共有多少个套集字有可读事件
          // 例如如果当前epoll对象中只有一个套接字有可读事件，那么epoll_wait就会把这个套接字的结构体存入events数组中
          // 而且是按顺序存放的，第一个有事件的套机字结构体就是下标为0的元素
          int count = epoll_wait(epoll_id, events, MAX_EVENT, -1);

          if (count == -1)
          {
               perror("epoll_wait");
               return 0;
          }

          for (size_t i = 0; i < count; i++)
          {
               int fd = events[i].data.fd;
               printf("fd = %d\n", fd);
               if (fd == sID) // 说明i是监听套接字
               {
                    printf("因为i(%lu)就是监听套接字，它上面发生的可读事件是说明有用户来连接我们了\n", i);
                    struct sockaddr_in clientAddr;
                    socklen_t len = sizeof(clientAddr);
                    int newFd = accept(fd, (struct sockaddr *)&clientAddr, &len);
                    printf("产生了一个新的套接字，快为它准一个结构体，并加入到epoll对象中管理起来吧\n");
                    ev.data.fd = newFd; // 把新的套接字赋值给结构体
                    // epoll操作和控制，EPOLL_CTL_ADD是往epoll对象中添加结构体
                    if (0 == epoll_ctl(epoll_id, EPOLL_CTL_ADD, newFd, &ev))
                    {
                         printf("套接字加入epoll成功\n");
                    }
               }
               else
               {
                    MSG msg;
                    int ret = read(fd, &msg, sizeof(MSG));
                    if (ret <= 0)
                    {
                         // 把要移除的套接字结构体准备好
                         ev.data.fd = fd;
                         // 把套接字结构体从epoll中移除掉
                         if (0 == epoll_ctl(epoll_id, EPOLL_CTL_DEL, fd, &ev))
                         {
                              printf("从epoll中移除套接字结构体成功\n");
                         }
                         else
                         {
                              perror("epoll");
                              printf("从epoll中移除套接字结构体失败\n");
                         }
                         close(fd);
                         continue; // 指向下一个循环
                    }
                    else
                    {
                         // printf("收到客户端的消息：%s\n", buf);
                         work(fd, msg);
                    }
               }
          }
     }
     close(sID);

     return 0;
}